﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Ue.CQRS.Events;
using Ue.CQRS.DistributionManager;
using Ue.CQRS.Repositories;

namespace Ue.CQRS.Tests.DistributionManager
{
    [TestClass]
    public class DistributorTests
    {
        private IDistributor distributor;
        private ConcurrentDictionary<string, ConcurrentQueue<Action<IEvent>>> eventHandlerCaches;
        private Mock<IRepository<string, ConcurrentQueue<Action<IEvent>>>> mockRepository;

        [TestInitialize]
        public void Initial()
        {
            this.eventHandlerCaches = new ConcurrentDictionary<string, ConcurrentQueue<Action<IEvent>>>();

            this.mockRepository = new Mock<IRepository<string, ConcurrentQueue<Action<IEvent>>>>();
            this.mockRepository.Setup(item => item.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<string, ConcurrentQueue<Action<IEvent>>>>()))
                .Returns<string, Func<string, ConcurrentQueue<Action<IEvent>>>>((eventId, queue) => this.eventHandlerCaches.GetOrAdd(eventId, queue));

            this.distributor = new Distributor(this.mockRepository.Object);
        }

        #region Register

        [TestMethod]
        public void HandleIsNullShouldThrowException()
        {
            try
            {
                this.distributor.Register("eventId", (Action<IEvent>)null);
                Assert.Fail("当handler是null时应抛出ArgumentNullException异常。");                
            }
            catch (ArgumentNullException)
            {
            }
        }

        [TestMethod]
        public void EventNameIsNullOrEmptyShouldThrowException()
        {
            Action<string> areEqual = (args) =>
            {
                try
                {
                    this.distributor.Register(args, new Action<IEvent>(d => { }));

                    Assert.Fail("当eventName是null或empty时应抛出异常。");
                }
                catch (DomainException)
                {
                }
            };

            areEqual(null);
            areEqual(string.Empty);
            areEqual("");
        }

        [TestMethod]
        public void AddToRepositoryAfterRegistration()
        {

            var eventName = "mockEvent";
            var mockEvent=new Mock<IEvent>();
            var handle = new Action<IEvent>(@event =>
            {
                Assert.AreEqual(mockEvent.Object,@event);
            });

            this.distributor.Register(eventName, handle);

            Assert.AreEqual(1, eventHandlerCaches.Count);
            Assert.AreEqual(eventName, eventHandlerCaches.Keys.ElementAt(0));

            Assert.AreEqual(1, eventHandlerCaches[eventName].Count);
            eventHandlerCaches[eventName].ElementAt(0)(mockEvent.Object);
        }

        [TestMethod]
        public void HandleRegisterToMultipleEvent()
        {
            var eventArray = new[] { "Event-1", "Event-2", "Event-3" };
            var mockEvent = new Mock<IEvent>();
            var handleExecutionCount = 0;
            var handle = new Action<IEvent>(@event =>
            {
                handleExecutionCount++;

                Assert.AreEqual(mockEvent.Object, @event);
            });

            foreach (var item in eventArray)
            {
                this.distributor.Register(item, handle);

                Assert.AreEqual(item, this.eventHandlerCaches.FirstOrDefault().Key);
                this.eventHandlerCaches.LastOrDefault().Value.FirstOrDefault()(mockEvent.Object);
            }

            Assert.AreEqual(eventArray.Length, handleExecutionCount);
            Assert.AreEqual(eventArray.Length, this.eventHandlerCaches.Count);
        }

        [TestMethod]
        public void RegisterMultipleHandle()
        {
            var eventName = "event1";
            var mockEvent = new Mock<IEvent>();
            var handleExecutionCount = 0;
            var handle = new Action<IEvent>(@event =>
            {
                handleExecutionCount++;
                Assert.AreEqual(mockEvent.Object, @event);
            });

            this.distributor.Register(eventName, handle);
            this.distributor.Register(eventName, handle);

            Assert.AreEqual(1, this.eventHandlerCaches.Count());
            Assert.AreEqual(2, this.eventHandlerCaches.FirstOrDefault().Value.Count);

            this.eventHandlerCaches.LastOrDefault().Value.FirstOrDefault()(mockEvent.Object);
            Assert.AreEqual(1, handleExecutionCount);

            this.eventHandlerCaches.LastOrDefault().Value.LastOrDefault()(mockEvent.Object);
            Assert.AreEqual(2, handleExecutionCount);
        }

        #endregion

        #region Dispatch

        [TestMethod]
        public void Dispatch()
        {
            var onEvent = Builder.Event("event1");

            var handleExecutionCount = 0;
            var handle = new Action<IEvent>(@event =>
            {
                handleExecutionCount++;
            });

            this.distributor.Register(onEvent.Name, handle);

            this.distributor.Dispatch(onEvent);
            Assert.AreEqual(1, handleExecutionCount);
        }

        [TestMethod]
        public void DispatchMultipleEvent()
        {
            var eventArray = Builder.Events("event1", "event2", "event3").ToList();
            var handleExecutionCount = 0;
            var handle = new Action<IEvent>(@event =>
            {
                handleExecutionCount++;
            });

            foreach (var item in eventArray)
            {
                this.distributor.Register(item.Name, handle);
            }
            foreach (var item in eventArray)
            {
                this.distributor.Dispatch(item);
            }

            Assert.AreEqual(eventArray.Count(), handleExecutionCount);
        }

        [TestMethod]
        public void DispatchMultipleHandle()
        {
            var onEvent = Builder.Event("mockEvent");
            var handleExecutionCount = 0;
            var handle = new Action<IEvent>(@event =>
            {
                handleExecutionCount++;
            });

            for (var i = 0; i < 3; i++)
            {
                this.distributor.Register(onEvent.Name, handle);
            }

            this.distributor.Dispatch(onEvent);
            Assert.AreEqual(3, handleExecutionCount);
        }

        #endregion

        #region Dispatch Events

        [TestMethod]
        public void Dispatching()
        {
            bool isDispatching = false;
            this.distributor.Dispatching += new EventHandler<DispatchEventArgs>((sender, arg) =>
            {
                isDispatching = true;
            });

            var domainEvent = Builder.Event("event1");
            this.distributor.Register(domainEvent.Name, new Action<IEvent>(d => { }));
            this.distributor.Dispatch(domainEvent);

            Assert.IsTrue(isDispatching);
        }

        [TestMethod]
        public void DispatchFailed()
        {
            bool isDispatchFailed = false;
            this.distributor.DispatchFailed += new EventHandler<DispatchEventArgs>((sender, arg) =>
            {
                isDispatchFailed = true;
            });

            var domainEvent = Builder.Event("event1");

            this.distributor.Register(domainEvent.Name, new Action<IEvent>(d => { }));
            this.distributor.Dispatch(domainEvent);
            Assert.IsFalse(isDispatchFailed);

            this.distributor.Register(domainEvent.Name, new Action<IEvent>(d => { throw new NotImplementedException(); }));
            this.distributor.Dispatch(domainEvent);
            Assert.IsTrue(isDispatchFailed);
        }

        [TestMethod]
        public void Dispatched()
        {
            bool isDispatched = false;
            this.distributor.Dispatched += new EventHandler<DispatchEventArgs>((sender, arg) =>
            {
                isDispatched = true;
            });

            var domainEvent = Builder.Event("event1");

            this.distributor.Register(domainEvent.Name, new Action<IEvent>(d => { }));
            this.distributor.Dispatch(domainEvent);
            Assert.IsTrue(isDispatched);
        }

        [TestMethod]
        public void DispatchFailedShouldNotExecutionDispatched()
        {
            bool isDispatchFailed = false;
            bool isDispatched = false;
            this.distributor.DispatchFailed += new EventHandler<DispatchEventArgs>((sender, arg) =>
            {
                isDispatchFailed = true;
            });
            this.distributor.Dispatched += new EventHandler<DispatchEventArgs>((sender, arg) =>
            {
                isDispatched = true;
            });

            var domainEvent = Builder.Event("event1");

            this.distributor.Register(domainEvent.Name, new Action<IEvent>(d => { throw new NotImplementedException(); }));
            this.distributor.Dispatch(domainEvent);
            Assert.IsTrue(isDispatchFailed);
            Assert.IsFalse(isDispatched);

        }

        #endregion
    }
}
